﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Activities.Presentation;
using System.Activities.Statements;
using System.Activities.Core.Presentation;
using System.Activities.Presentation.Toolbox;
using System.Activities;
using System.Activities.Presentation.View;
using System.Reflection;
using System.Windows.Controls.Primitives;
using System.Activities.Presentation.Hosting;
using System.Activities.XamlIntegration;
using System.IO;
using Wilke.Interactive.Drone.Contracts;
using Wilke.Interactive.Drone.Control;
using Wilke.Interactive.Drone.Control.Enumerations;
using Microsoft.Win32;
using System.Activities.Debugger;
using System.Activities.Presentation.Services;
using System.Activities.Presentation.Debug;
using System.Activities.Tracking;
using System.Windows.Threading;
using System.Threading;
using System.Activities.Presentation.Metadata;
using System.Drawing;
using System.Collections.ObjectModel;

namespace Wilke.Interactive.Drone.Extensions
{
    /// <summary>
    /// Interaction logic for PathDesigner.xaml
    /// </summary>
    public partial class PathDesigner : Window
    {
        #region Private Properties

        private WorkflowDesigner WorkflowDesigner { get; set; }
        private ToolboxControl ToolboxControl { get; set; }
        private IDesignerDebugView DebuggerService { get; set; }
        private DesignerView DesignerView { get; set; }

        private Dictionary<object, SourceLocation> SourceLocationMap { get; set; }
        private Dictionary<string, Activity> ElementMap { get; set; }
        private Dictionary<string, Activity> CustomTrackMap { get; set; }

        private double column1Width;
        private double column2Width;
        private string currentWorkflowFileName = String.Empty;
        
        private const string DroneCommanderArgumentName = "DroneCommander";

        private bool isDirty = false;
        private bool testMode = true;
        private bool loadedFromDisk = false;

        #endregion

        #region Public Properties

        public DroneController DroneController { get; set; }

        #endregion

        #region Construction

        public PathDesigner(DroneController droneController)
            : this()
        {
            this.DroneController = droneController;
        }

        public PathDesigner()
        {
            InitializeComponent();
            RegisterMetaData();
            HostWorkflowDesigner();

            HostToolbox();
            HostPropertyGrid();

            canvas.ColumnDefinitions[1].Width = new GridLength(200, GridUnitType.Pixel);
            canvas.ColumnDefinitions[5].Width = new GridLength(240, GridUnitType.Pixel);
        }

        #endregion

        #region Private Methods

        private void HostWorkflowDesigner()
        {
            LoadWorkflow(Properties.Resources.WorkflowTemplate, false);
        }

        private void HostToolbox()
        {
            ToolboxItemWrapper toolboxItemWrapper = null;
            this.ToolboxControl = new ToolboxControl();

            #region Standard Activities

            ToolboxCategory toolboxCategory = new ToolboxCategory("Standard Activities");

            #region Add Icons for standard activities.

            AttributeTableBuilder attributeTableBuilder = new AttributeTableBuilder();
            ToolboxBitmapAttribute toolboxBitmapAttribute = new ToolboxBitmapAttribute(typeof(PathDesigner), "Resources.Icons.Parallel.png");
            attributeTableBuilder.AddCustomAttributes(typeof(System.Activities.Statements.Parallel), toolboxBitmapAttribute);

            toolboxBitmapAttribute = new ToolboxBitmapAttribute(typeof(PathDesigner), "Resources.Icons.WriteLine.png");
            attributeTableBuilder.AddCustomAttributes(typeof(System.Activities.Statements.WriteLine), toolboxBitmapAttribute);

            MetadataStore.AddAttributeTable(attributeTableBuilder.CreateTable());

            #endregion

            #region Add Toolbox Items

            toolboxItemWrapper = new ToolboxItemWrapper(typeof(System.Activities.Statements.Parallel));
            toolboxItemWrapper.DisplayName = "Parallel";
            toolboxCategory.Add(toolboxItemWrapper);

            toolboxItemWrapper = new ToolboxItemWrapper(typeof(System.Activities.Statements.WriteLine));
            toolboxItemWrapper.DisplayName = "WriteLine";
            toolboxCategory.Add(toolboxItemWrapper);

            #endregion

            ToolboxControl.Categories.Add(toolboxCategory);

            #endregion

            #region Custom Activities

            toolboxCategory = new ToolboxCategory("ARDrone Activities");

            #region Add Toolbox Items

            toolboxItemWrapper = new ToolboxItemWrapper(typeof(Wilke.Interactive.Drone.Extensions.Activities.TakeOffActivity));
            toolboxItemWrapper.DisplayName = "Take Off";
            toolboxCategory.Add(toolboxItemWrapper);

            toolboxItemWrapper = new ToolboxItemWrapper(typeof(Wilke.Interactive.Drone.Extensions.Activities.LandActivity));
            toolboxItemWrapper.DisplayName = "Land";
            toolboxCategory.Add(toolboxItemWrapper);

            toolboxItemWrapper = new ToolboxItemWrapper(typeof(Wilke.Interactive.Drone.Extensions.Activities.PilotActivity));
            toolboxItemWrapper.DisplayName = "Pilot";
            toolboxCategory.Add(toolboxItemWrapper);

            toolboxItemWrapper = new ToolboxItemWrapper(typeof(Wilke.Interactive.Drone.Extensions.Activities.PlayLedActivity));
            toolboxItemWrapper.DisplayName = "Play led animation";
            toolboxCategory.Add(toolboxItemWrapper);

            toolboxItemWrapper = new ToolboxItemWrapper(typeof(Wilke.Interactive.Drone.Extensions.Activities.WaitActivity));
            toolboxItemWrapper.DisplayName = "Wait";
            toolboxCategory.Add(toolboxItemWrapper);

            toolboxItemWrapper = new ToolboxItemWrapper(typeof(Wilke.Interactive.Drone.Extensions.Activities.StartVideoActivity));
            toolboxItemWrapper.DisplayName = "Start Video";
            toolboxCategory.Add(toolboxItemWrapper);

            toolboxItemWrapper = new ToolboxItemWrapper(typeof(Wilke.Interactive.Drone.Extensions.Activities.SwitchVideoActivity));
            toolboxItemWrapper.DisplayName = "Switch Video Channel";
            toolboxCategory.Add(toolboxItemWrapper);

            toolboxItemWrapper = new ToolboxItemWrapper(typeof(Wilke.Interactive.Drone.Extensions.Activities.StopVideoActivity));
            toolboxItemWrapper.DisplayName = "Stop Video";
            toolboxCategory.Add(toolboxItemWrapper);

            #endregion

            ToolboxControl.Categories.Add(toolboxCategory);

            #endregion

            toolboxControlPanel.Children.Add(this.ToolboxControl);
        }

        private void HostPropertyGrid()
        {
            propertiesGridPanel.Children.Add(this.WorkflowDesigner.PropertyInspectorView);
        }

        private void RegisterMetaData()
        {
            (new DesignerMetadata()).Register();
        }

        private void ClearWorkflowDesigner()
        {
            if (this.WorkflowDesigner != null)
            {
                designerPanel.Children.Remove(this.WorkflowDesigner.View);
                propertiesGridPanel.Children.Remove(this.WorkflowDesigner.PropertyInspectorView);
                this.WorkflowDesigner = null;
            }
        }

        private bool LoadWorkflow(string workflow, bool isPath)
        {
            WorkflowDesigner workflowDesigner = new WorkflowDesigner();
            if (isPath)
            {
                workflowDesigner.Load(workflow);
            }
            else
            {
                workflowDesigner.Text = workflow;
                workflowDesigner.Load();
            }

            if (!testMode)
            {
                DynamicActivity rootActivity = GetRuntimeExecutionRoot(workflowDesigner) as DynamicActivity;

                if (rootActivity != null)
                {
                    if (rootActivity.Properties.Count == 1)
                    {
                        if (!rootActivity.Properties[0].Name.Equals(DroneCommanderArgumentName))
                        {
                            MessageBox.Show("This is not an ARDrone pathdesigner flow.");
                            return false;
                        }
                    }
                    else
                    {
                        MessageBox.Show("This is not an ARDrone pathdesigner flow.");
                        return false;
                    }
                }
            }

            this.WorkflowDesigner = workflowDesigner;

            this.WorkflowDesigner.ModelChanged += new EventHandler(WorkflowDesigner_ModelChanged);
            
            this.DebuggerService = this.WorkflowDesigner.DebugManagerView;

            #region Customize DesignerView

            this.DesignerView = this.WorkflowDesigner.Context.Services.GetService<DesignerView>();

            ((UIElement)this.DesignerView.FindName("expandAllButton")).Visibility = Visibility.Hidden;
            ((UIElement)this.DesignerView.FindName("collapseAllButton")).Visibility = Visibility.Hidden;

            this.DesignerView.WorkflowShellBarItemVisibility = ShellBarItemVisibility.Arguments | ShellBarItemVisibility.Zoom;

            #region Temporary workaround to deal with gridsplitter issue and WorkflowDesigner rehosting (cursor does not appear)

            this.DesignerView.Loaded += (o, ev) => WorkflowDesigner.View.Dispatcher.BeginInvoke
            (DispatcherPriority.SystemIdle, new Action(() =>
              {
                  ((RoutedCommand)DesignerView.ToggleArgumentDesignerCommand).Execute(null, this.DesignerView);
                  ((RoutedCommand)DesignerView.ToggleArgumentDesignerCommand).Execute(null, this.DesignerView);
              }));
            ((UIElement)this.DesignerView.FindName("buttonArguments1")).Visibility = Visibility.Hidden;

            //this.DesignerView.SizeChanged += (o, ev) => WorkflowDesigner.View.Dispatcher.BeginInvoke
            //(DispatcherPriority.SystemIdle, new Action(() =>
            //{
            //    ((RoutedCommand)DesignerView.FitToScreenCommand).Execute(null, this.DesignerView);
            //}));

            #endregion
            
            #endregion

            designerPanel.Children.Add(this.WorkflowDesigner.View);

            return true;
        }

        void WorkflowDesigner_ModelChanged(object sender, EventArgs e)
        {
            isDirty = true;
        }

        void ShowDebug(SourceLocation srcLoc)
        {
            this.Dispatcher.Invoke(DispatcherPriority.Render
                , (Action)(() =>
                {
                    this.WorkflowDesigner.DebugManagerView.CurrentLocation = srcLoc;
                }));
        }

        private Dictionary<object, SourceLocation> UpdateSourceLocationMappingInDebuggerService()
        {
            object rootInstance = GetRootInstance();
            Dictionary<object, SourceLocation> sourceLocationMapping = new Dictionary<object, SourceLocation>();
            Dictionary<object, SourceLocation> designerSourceLocationMapping = new Dictionary<object, SourceLocation>();

            if (rootInstance != null)
            {
                Activity documentRootElement = GetRootWorkflowElement(rootInstance);
                SourceLocationProvider.CollectMapping(GetRootRuntimeWorkflowElement(), documentRootElement, sourceLocationMapping, this.WorkflowDesigner.Context.Items.GetValue<WorkflowFileItem>().LoadedFile);
                SourceLocationProvider.CollectMapping(documentRootElement, documentRootElement, designerSourceLocationMapping, this.WorkflowDesigner.Context.Items.GetValue<WorkflowFileItem>().LoadedFile);
            }

            if (this.DebuggerService != null)
            {
                DebuggerService debuggerService = this.DebuggerService as DebuggerService;
                debuggerService.UpdateSourceLocations(designerSourceLocationMapping);
            }

            return sourceLocationMapping;
        }

        private Dictionary<string, Activity> BuildActivityIdToWfElementMap(Dictionary<object, SourceLocation> wfElementToSourceLocationMap)
        {
            Dictionary<string, Activity> map = new Dictionary<string, Activity>();

            Activity wfElement;
            foreach (object instance in wfElementToSourceLocationMap.Keys)
            {
                wfElement = instance as Activity;
                if (wfElement != null)
                {
                    map.Add(wfElement.Id, wfElement);
                }
            }

            return map;
        }

        private bool SaveWorkflow()
        {
            bool doSave = false;
            string workflowFileName = String.Empty;

            if (String.IsNullOrEmpty(currentWorkflowFileName))
            {
                SaveFileDialog saveFileDialog = new SaveFileDialog();
                saveFileDialog.DefaultExt = ".xaml";
                saveFileDialog.Filter = "ARDrone path designer documents (*.xaml) | *.xaml";

                bool? result = saveFileDialog.ShowDialog();

                if (result == true)
                {
                    doSave = true;
                    workflowFileName = saveFileDialog.FileName;
                }
            }
            else
            {
                doSave = true;
                workflowFileName = currentWorkflowFileName;
            }

            if (doSave)
            {
                this.WorkflowDesigner.Flush();
                File.WriteAllText(workflowFileName, this.WorkflowDesigner.Text);
                this.Title = workflowFileName;
                currentWorkflowFileName = workflowFileName;
                isDirty = false;
            }

            return doSave;
        }

        # region Helper Methods

        private Activity GetRuntimeExecutionRoot()
        {
            return GetRuntimeExecutionRoot(this.WorkflowDesigner);
        }

        private Activity GetRuntimeExecutionRoot(WorkflowDesigner workflowDesigner)
        {
            Activity rootActivity = null;

            using (StringReader stringReader = new StringReader(workflowDesigner.Text))
            {
                workflowDesigner.Flush();
                rootActivity = ActivityXamlServices.Load(stringReader);
                WorkflowInspectionServices.CacheMetadata(rootActivity);
            }
            return rootActivity;
        }

        private Activity GetRootRuntimeWorkflowElement()
        {
            Activity rootActivity = GetRuntimeExecutionRoot();

            IEnumerator<Activity> activities = WorkflowInspectionServices.GetActivities(rootActivity).GetEnumerator();
            activities.MoveNext();
            Activity workflowElement = activities.Current;
            return workflowElement;
        }

        private object GetRootInstance()
        {
            ModelService modelService = this.WorkflowDesigner.Context.Services.GetService<ModelService>();
            if (modelService != null)
            {
                return modelService.Root.GetCurrentValue();
            }
            else
            {
                return null;
            }
        }

        Activity GetRootWorkflowElement(object rootModelObject)
        {
            System.Diagnostics.Debug.Assert(rootModelObject != null, "Cannot pass null as rootModelObject");

            Activity rootWorkflowElement;
            IDebuggableWorkflowTree debuggableWorkflowTree = rootModelObject as IDebuggableWorkflowTree;
            if (debuggableWorkflowTree != null)
            {
                rootWorkflowElement = debuggableWorkflowTree.GetWorkflowRoot();
            }
            else // Loose xaml case.
            {
                rootWorkflowElement = rootModelObject as Activity;
            }
            return rootWorkflowElement;
        }

        #endregion

        #endregion

        #region Event Handlers

        private void SelectionChanged(Selection selection)
        { }

        private void buttonCollapseLeft_Click(object sender, RoutedEventArgs e)
        {
            column1Width = canvas.ColumnDefinitions[1].ActualWidth;
            canvas.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Pixel);
            parkZoneLeft.Visibility = System.Windows.Visibility.Visible;
            toolboxControlPanel.Visibility = System.Windows.Visibility.Collapsed;
            gridSplitterColumn12.IsEnabled = false;
        }

        private void parkZoneLeft_MouseDown(object sender, MouseButtonEventArgs e)
        {
            canvas.ColumnDefinitions[1].Width = new GridLength(column1Width, GridUnitType.Pixel);
            parkZoneLeft.Visibility = System.Windows.Visibility.Collapsed;
            toolboxControlPanel.Visibility = System.Windows.Visibility.Visible;
            gridSplitterColumn12.IsEnabled = true;
        }

        private void buttonCollapseRight_Click(object sender, RoutedEventArgs e)
        {
            column2Width = canvas.ColumnDefinitions[5].ActualWidth;
            canvas.ColumnDefinitions[5].Width = new GridLength(0, GridUnitType.Pixel);
            parkZoneRight.Visibility = System.Windows.Visibility.Visible;
            propertiesGridPanel.Visibility = System.Windows.Visibility.Collapsed;
            gridSplitterColumn23.IsEnabled = false;
        }

        private void parkZoneRight_MouseDown(object sender, MouseButtonEventArgs e)
        {
            canvas.ColumnDefinitions[5].Width = new GridLength(column2Width, GridUnitType.Pixel);
            parkZoneRight.Visibility = System.Windows.Visibility.Collapsed;
            propertiesGridPanel.Visibility = System.Windows.Visibility.Visible;
            gridSplitterColumn23.IsEnabled = true;
        }

        private void gridSplitterColumn12_DragCompleted(object sender, DragCompletedEventArgs e)
        {
            column1Width = canvas.ColumnDefinitions[1].ActualWidth;
        }

        private void gridSplitterColumn23_DragCompleted(object sender, DragCompletedEventArgs e)
        {
            column2Width = canvas.ColumnDefinitions[5].ActualWidth;
        }

        private void buttonOpen_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.DefaultExt = ".xaml";
            openFileDialog.Filter = "ARDrone path designer documents (*.xaml) | *.xaml";

            bool? result = openFileDialog.ShowDialog();

            if (result == true)
            {   
                bool success = LoadWorkflow(openFileDialog.FileName, true);

                if (success)
                {                    
                    WorkflowDesigner.View.Dispatcher.BeginInvoke
                    (DispatcherPriority.SystemIdle, new Action(() =>
                    {
                        ((RoutedCommand)DesignerView.FitToScreenCommand).Execute(null, this.DesignerView);
                    }));
                    this.Title = openFileDialog.FileName;
                    currentWorkflowFileName = openFileDialog.FileName;
                    loadedFromDisk = true;
                }
            }
        }

        private void ButtonSave_Click(object sender, RoutedEventArgs e)
        {
            SaveWorkflow();
        }

        private void buttonRun_Click(object sender, RoutedEventArgs e)
        {
            WorkflowApplication workflowApplication = null; 
            bool doContinue = false;            

            if (DroneController == null)
            {
                doContinue = true;
            }
            else
            {
                if (DroneController.ConnectionStatus == ConnectionStatus.Open)
                {
                    doContinue = true;
                }
            }

            if (doContinue)
            {
                this.WorkflowDesigner.Flush();

                if (checkBoxTrackWorkflow.IsChecked.Value)
                {
                    bool doReload = false;

                    if (!loadedFromDisk)
                    {
                        doReload = true;
                    }
                    else
                    {
                        doReload = isDirty;
                    }

                    //In we want to enable workflow tracking we need to load the workflow from a file in stead of memory. (Because of the mapping between modelelements and position in xaml file)
                    if (doReload)
                    {
                        bool saveFile = SaveWorkflow();
                        if (!saveFile)
                        {
                            return;
                        }

                        LoadWorkflow(currentWorkflowFileName, true);
                    }
                }

                DynamicActivity rootActivity = GetRuntimeExecutionRoot() as DynamicActivity;

                if (rootActivity != null)
                {
                    #region Add Arguments

                    if (rootActivity.Properties.Count > 0)
                    {
                        Dictionary<string, object> arguments = new Dictionary<string, object>();

                        if (rootActivity.Properties[0].Name.Equals(DroneCommanderArgumentName))
                        {
                            arguments.Add(DroneCommanderArgumentName, DroneController);                            
                        }
                        else
                        {
                            #region Try to populate arguments with default values, this is only useful to test external created workflows during testing of the pathdesigner.

                            foreach (var property in rootActivity.Properties)
                            {
                                Type type = (Type)property.Value.GetType().GetProperty("ArgumentType").GetValue(property.Value, null);

                                switch (type.Name)
                                {
                                    case "Boolean":
                                        arguments.Add(property.Name, false);
                                        break;
                                    default:
                                        arguments.Add(property.Name, null);
                                        break;
                                }
                            }

                            #endregion
                        }

                        workflowApplication = new WorkflowApplication(rootActivity, arguments);
                    }
                    else
                    {
                        workflowApplication = new WorkflowApplication(rootActivity);
                    }

                    #endregion

                    if (checkBoxTrackWorkflow.IsChecked.Value)
                    {
                        # region Set up Custom Tracking

                        SourceLocationMap = UpdateSourceLocationMappingInDebuggerService();
                        ElementMap = BuildActivityIdToWfElementMap(SourceLocationMap);                        

                        VisualTrackingParticipant simulationTracker = new VisualTrackingParticipant();

                        TrackingProfile trackingProfile = new TrackingProfile();
                        trackingProfile.Name = "CustomTrackingProfile";

                        CustomTrackingQuery customTrackingQuery = new CustomTrackingQuery();
                        customTrackingQuery.Name = "*";
                        customTrackingQuery.ActivityName = "*";

                        WorkflowInstanceQuery workflowInstanceQuery = new WorkflowInstanceQuery();
                        workflowInstanceQuery.States.Add(WorkflowInstanceStates.Started);
                        workflowInstanceQuery.States.Add(WorkflowInstanceStates.Completed);

                        ActivityStateQuery activityStateQuery = new ActivityStateQuery();
                        activityStateQuery.ActivityName = "*";
                        activityStateQuery.States.Add("*");
                        activityStateQuery.Variables.Add("*");

                        trackingProfile.Queries.Add(customTrackingQuery);
                        trackingProfile.Queries.Add(workflowInstanceQuery);
                        trackingProfile.Queries.Add(activityStateQuery);

                        trackingProfile.ImplementationVisibility = ImplementationVisibility.All;

                        simulationTracker.TrackingProfile = trackingProfile;
                        simulationTracker.ActivityIdToWorkflowElementMap = ElementMap;
                        simulationTracker.TrackingRecordReceived += new EventHandler<TrackingEventArgs>(simTracker_TrackingRecordReceived);

                        # endregion

                        workflowApplication.Extensions.Add(simulationTracker);

                        #region Track Root Activity

                        Activity activity = ElementMap.Values.First();
                        ShowDebug(SourceLocationMap[activity]);

                        #endregion
                    }

                    workflowApplication.Run();
                }
                else
                {
                    MessageBox.Show("Could not cast the root activity to a dynamic activity.");
                }
            }
        }

        void simTracker_TrackingRecordReceived(object sender, TrackingEventArgs e)
        {
            Activity activity = null;

            if (e.Activity != null)
            {
                //ShowDebug(SourceLocationMap[e.Activity]);

                if (e.Activity == ElementMap.Values.First())
                {
                    ActivityStateRecord activityStateRecord = e.Record as ActivityStateRecord;

                    if (activityStateRecord != null)
                    {
                        if (activityStateRecord.State == "Closed")
                        {
                            activity = ElementMap.Values.First();
                        }
                        else 
                        {
                            activity = GetNextActivity(e.Activity.Id);
                        }
                    }
                }
                else
                {
                    activity = GetNextActivity(e.Activity.Id);
                }

                if (activity != null)
                {
                    ShowDebug(SourceLocationMap[activity]);
                }
                else
                {
                    activity = ElementMap.Values.First();

                    ShowDebug(SourceLocationMap[activity]);
                }
                this.Dispatcher.Invoke(DispatcherPriority.SystemIdle, (Action)(() =>
                {
                    System.Threading.Thread.Sleep(250);
                }));
            }
        }

        private Activity GetNextActivity(string activityId)
        {
            bool takeNext = false;
            Activity activity = null;

            foreach (KeyValuePair<string, Activity> entry in ElementMap)
            {
                if (takeNext)
                {
                    activity = entry.Value;
                    break;
                }
                if (entry.Key == activityId)
                {
                    takeNext = true;
                }
            }

            return activity;
        }

        #endregion
    }
}
